package fr.univ.lr.icone.gui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.util.Date;
import java.util.HashMap;
import java.util.concurrent.LinkedBlockingQueue;

import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.SpinnerModel;
import javax.swing.SpinnerNumberModel;
import javax.swing.border.LineBorder;
import javax.swing.border.TitledBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import fr.univ.lr.icone.configuration.Configuration;
import fr.univ.lr.icone.gui.event.Event;
import fr.univ.lr.icone.zone.Zone;

/**
 * Class that implements a GUI (Graphic User Interface)
 * 
 * @author Elian ORIOU
 * 
 */

public class GraphicUI extends Thread implements IEventListener {

	/* Constants */
	private static final String AGENTS_NUMBER_STRING = "Agent Number = ";

	private static final String SIMULATION_PANEL_TITLE = "Simulation";
	private static final String STEP_BY_STEP_PANEL_TITLE = "Step By Step";
	private static final String INFORMATION_PANEL_TITLE = "Informations";
	private static final String SCENARIO_PANEL_TITLE = "Scenario";
	private static final String OPINION_LEGEND_PANEL_TITLE = "Opinion Legend";

	private static final String SCENARIO_LOAD_BUTTON = "Load Scenario";

	private static final String BUTTON_START_TEXT = "Start/Resume";
	private static final String BUTTON_PAUSE_TEXT = "Pause";
	private static final String BUTTON_RESET_TEXT = "Restart";
	private static final String BUTTON_QUIT_TEXT = "Quit";
	private static final String NEXT_BUTTON_TEXT = "Next";
	private static final String PREVIOUS_BUTTON_TEXT = "Previous";
	private static final String CHART_CBUTTON_STRING = "Number(Opinion) chart";
	private static final String TIME_DELAY_CONTROL_LABEL_STRING = "Time Delay";

	private static final int MAX_EVENT = 50;

	/* The Controler : The Zone */
	private Zone zoneControler;

	/* The event queue */
	private LinkedBlockingQueue<Event> eventQueue;
	private HashMap<Point, Integer> agents; /* Position --> Opinion */
	private int agentNumber;
	public boolean active;

	/* Simulation Parameters */
	private int simulationTimeDelay;

	private SpinnerModel model;
	private JSpinner timeSpinner;

	/* Containers (Frame & Panels) */
	private JFrame frame;
	private JPanel rootPanel;
	private JPanel simulationPanel;
	private JPanel scenarioPanel;
	private JPanel informationPanel;
	private JPanel stepByStepPanel;
	private JPanel rightPanel;

	private JFileChooser fileChooser;

	/* Buttons */
	private JButton b_start;
	private JButton b_resume_pause;
	private JButton b_quit;
	private JButton b_restart;

	private JButton b_chart;

	private JButton b_loadScenario;

	private JButton b_nextStep;
	private JButton b_prevStep;

	/* Layouts */
	private BorderLayout rootLayout;
	private BoxLayout simulationLayout;
	private BoxLayout scenarioLayout;
	private BoxLayout rightLayout;
	private BoxLayout stepByStepLayout;

	/* Legend Component */
	private OpinionLegend legend;

	/* The drawing zone */
	private DrawingZone zone;

	/* Labels */
	private JLabel agentNumber_lbl;
	private JLabel timeDelay_lbl;

	/* Number(pinion) chart */
	private AgentPopulationChartPanel chart;

	/* Buttons Listeners */
	private GUIEventControler controler;

	/**
	 * 
	 * @param zone
	 */

	public GraphicUI() {
		this.eventQueue = new LinkedBlockingQueue<Event>();
		this.active = false;
		this.agents = new HashMap<Point, Integer>();
		this.agentNumber = agents.size();
		this.simulationTimeDelay = 100;
	}

	/**
	 * Initializes the component
	 */

	private void init() {
		frame = new JFrame("Opinion Simulator 1.0");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	}

	/**
	 * Builds the GUI with SWING components and manages layouts
	 */

	private void buildGUI() {
		/* Panels */
		rootPanel = new JPanel();
		simulationPanel = new JPanel();
		stepByStepPanel = new JPanel();
		scenarioPanel = new JPanel();
		rightPanel = new JPanel();
		informationPanel = new JPanel();
		chart = new AgentPopulationChartPanel();

		/* Borders */
		simulationPanel.setBorder(new TitledBorder(new LineBorder(Color.black,
				1, false), SIMULATION_PANEL_TITLE, TitledBorder.CENTER,
				TitledBorder.BELOW_TOP));

		scenarioPanel.setBorder(new TitledBorder(new LineBorder(Color.black, 1,
				false), SCENARIO_PANEL_TITLE, TitledBorder.CENTER,
				TitledBorder.BELOW_TOP));

		stepByStepPanel.setBorder(new TitledBorder(new LineBorder(Color.black,
				1, false), STEP_BY_STEP_PANEL_TITLE, TitledBorder.CENTER,
				TitledBorder.BELOW_TOP));

		/* Layouts */
		rootLayout = new BorderLayout();
		rootPanel.setLayout(rootLayout);
		simulationLayout = new BoxLayout(simulationPanel, BoxLayout.PAGE_AXIS);
		simulationPanel.setLayout(simulationLayout);
		scenarioLayout = new BoxLayout(scenarioPanel, BoxLayout.PAGE_AXIS);
		scenarioPanel.setLayout(scenarioLayout);
		rightLayout = new BoxLayout(rightPanel, BoxLayout.PAGE_AXIS);
		rightPanel.setLayout(rightLayout);

		/* Buttons */
		b_start = new JButton(BUTTON_START_TEXT);
		b_resume_pause = new JButton(BUTTON_PAUSE_TEXT);
		b_quit = new JButton(BUTTON_QUIT_TEXT);
		b_restart = new JButton(BUTTON_RESET_TEXT);
		b_chart = new JButton(CHART_CBUTTON_STRING);
		b_loadScenario = new JButton(SCENARIO_LOAD_BUTTON);
		b_prevStep = new JButton(PREVIOUS_BUTTON_TEXT);
		b_nextStep = new JButton(NEXT_BUTTON_TEXT);

		/* Time Delay Control */
		timeDelay_lbl = new JLabel(TIME_DELAY_CONTROL_LABEL_STRING);
		model = new SpinnerNumberModel(100, 1, 2000, 100);
		timeSpinner = new JSpinner(model);
		timeSpinner.setSize(new Dimension(15, 15));

		/* The Drawing zone */
		zone = new DrawingZone(Configuration.getGridSize());
		zone.setSize(800, 800);

		/* Simulation Panel */
		simulationPanel.add(b_start);
		simulationPanel.add(b_resume_pause);
		simulationPanel.add(b_restart);
		simulationPanel.add(b_chart);
		simulationPanel.add(b_quit);

		/* ScenarioPanel */
		scenarioPanel.add(b_loadScenario);

		/* Step by step panel */
		stepByStepLayout = new BoxLayout(stepByStepPanel, BoxLayout.PAGE_AXIS);
		stepByStepPanel.setLayout(stepByStepLayout);
		stepByStepPanel.add(b_prevStep);
		stepByStepPanel.add(b_nextStep);

		/* Opinion Legend */
		legend = new OpinionLegend();
		legend.setBorder(new TitledBorder(
				new LineBorder(Color.black, 1, false),
				OPINION_LEGEND_PANEL_TITLE, TitledBorder.CENTER,
				TitledBorder.BELOW_TOP));

		/* The information panel */
		informationPanel.setBorder(new TitledBorder(new LineBorder(Color.black,
				1, false), INFORMATION_PANEL_TITLE, TitledBorder.CENTER,
				TitledBorder.BELOW_TOP));

		agentNumber_lbl = new JLabel(AGENTS_NUMBER_STRING + agentNumber);

		informationPanel.add(agentNumber_lbl);
		informationPanel.add(timeDelay_lbl);
		informationPanel.add(timeSpinner);

		/* About Scenario Replay */
		fileChooser = new JFileChooser("C:\\Users\\Elian");

		/* Right Panel controls */
		rightPanel.add(simulationPanel, BorderLayout.LINE_END);
		rightPanel.add(scenarioPanel, BorderLayout.LINE_END);
		rightPanel.add(stepByStepPanel, BorderLayout.LINE_END);
		rightPanel.add(legend, BorderLayout.LINE_END);

		rootPanel.add(zone, BorderLayout.CENTER);
		rootPanel.add(rightPanel, BorderLayout.LINE_END);
		rootPanel.add(informationPanel, BorderLayout.PAGE_END);

		/* Fixes the rootPanel to the frame */
		frame.setContentPane(rootPanel);

		log("GUI built !");
	}

	/**
	 * Sets a listener on a component (to recover actions)
	 */

	private void setListeners() {
		controler = new GUIEventControler();
		b_start.addActionListener(controler);
		b_resume_pause.addActionListener(controler);
		b_restart.addActionListener(controler);
		b_chart.addActionListener(controler);
		b_quit.addActionListener(controler);
		b_loadScenario.addActionListener(controler);
		b_nextStep.addActionListener(controler);
		b_prevStep.addActionListener(controler);
		timeSpinner.addChangeListener(controler);
	}

	/**
	 * Sets the simulation step delay
	 * 
	 * @param timeDelay
	 */

	public void setSimulationTimeDelay(int timeDelay) {
		simulationTimeDelay = timeDelay;
	}

	/**
	 * Refreshes the opinion number chart values
	 */

	public void refreshChart() {
		int[] values = new int[10];
		for (int i = 0; i < 10; i++) {
			values[i] = 0;
			for (int opinion : agents.values()) {
				if (opinion == i) {
					values[i]++;
				}
			}
		}
		chart.refresh(values);
	}

	@Override
	public void receiveEvent(Event e) {
		try {
			/*
			 * if (eventQueue.size() > MAX_EVENT) { eventQueue.put(e);
			 * zoneControler.setPause(true); return; }
			 */
			// zoneControler.setPause(false);
			eventQueue.put(e);
			log("RECEIVES EVENT : " + e);
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}
	}

	@Override
	public void processEvent() throws Exception {
		Event e = eventQueue.poll();
		if (e == null) {
			return;
		}
		log("Processes Event : " + e);
		switch (e.getEventType()) {
		case AGENT_CREATION:
			if (e.getOpinion() == -1 || e.getPosition() == null) {
				throw new Exception("Missing opinion & position !");
			}
			zone.addAgent(e.getOpinion(), e.getPosition());
			/* Adding agent to the list */
			agents.put(e.getPosition(), e.getOpinion());
			agentNumber = agents.size();
			agentNumber_lbl.setText(AGENTS_NUMBER_STRING + agentNumber);
			refreshChart();
			log("Adding Agent at " + e.getPosition() + " with "
					+ e.getOpinion());
			break;
		case OPINION_CHANGED:
			if (e.getOpinion() == -1 || e.getPosition() == null) {
				throw new Exception("Missing opinion & position !");
			}
			if (agents.get(e.getPosition()) == null) {
				return;
			}
			log("Opinion changed for " + e.getPosition());
			System.out.println(agents.size());
			zone.addAgent(e.getOpinion(), e.getPosition());
			agents.put(e.getPosition(), e.getOpinion());
			refreshChart();
			break;
		case POSITION_CHANGED:
			if (e.getOldPosition() == null || e.getPosition() == null
					|| e.getOpinion() == -1) {
				throw new Exception("Missing oldposition, position & opinion !");
			}
			log("Position changed at " + e.getOldPosition() + " to "
					+ e.getPosition());
			zone.removeAgent(e.getOldPosition());
			zone.addAgent(e.getOpinion(), e.getPosition());
			break;
		case ENABLED_AGENT:
			/* Inutile pour le moment */
			break;
		case DISABLED_AGENT:
			/* Inutile pour le moment */
			break;
		}
	}

	@Override
	public LinkedBlockingQueue<Event> getEventQueue() {
		return eventQueue;
	}

	/**
	 * Thread execution method
	 */

	@Override
	public void run() {
		log("Running GUI thread");
		init();
		buildGUI();
		setListeners();
		frame.setBounds(new Rectangle(800, 700));
		frame.setLocationRelativeTo(null);
		frame.setResizable(false);
		frame.setVisible(true);

		while (true) {
			while (!active) {
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			try {
				processEvent();
			} catch (Exception e) {
				e.printStackTrace();
			}
			try {
				sleep(simulationTimeDelay);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * Sets the controler
	 * 
	 * @param zone
	 */

	public void setZone(Zone zone) {
		this.zoneControler = zone;
	}

	public void clearContent() {
		eventQueue.clear();
		zone.clearAgents();
		agents.clear();
	}

	/**
	 * Inner class that processes all GUI events
	 * 
	 * @author Elian ORIOU
	 * 
	 */

	class GUIEventControler implements ActionListener, ChangeListener {

		public GUIEventControler() {
		}

		/**
		 * Recovers actions from components
		 */

		@Override
		public void actionPerformed(ActionEvent e) {
			if (e.getSource().equals(b_start)) {
				// START PROCESS
				active = true;
				zoneControler.setPause(false);
				try {
				} catch (Exception e1) {
					e1.printStackTrace();
				}
			} else if (e.getSource().equals(b_resume_pause)) {
				// PAUSE PROCESS
				if (active == true) {
					active = false;
					zoneControler.setPause(true);
				}
				// RESUME PROCESS
				else {
					active = true;
					zoneControler.setPause(false);
				}
			} else if (e.getSource().equals(b_chart)) {
				// CHART FRAME OPENNING
				JFrame chartFrame = new JFrame("Number(Opinion) Chart");
				chartFrame.setBounds(new Rectangle(550, 325));
				chartFrame.setContentPane(chart);
				chartFrame.setVisible(true);
			} else if (e.getSource().equals(b_quit)) {
				frame.dispose();
				System.exit(0);
			} else if (e.getSource().equals(b_restart)) {
				// zoneControler.restart();
			} else if (e.getSource().equals(b_loadScenario)) {
				// LOAD A SCENARIO
				int returnVal = fileChooser.showOpenDialog(new JFrame(
						"Choose a file"));
				if (returnVal == JFileChooser.APPROVE_OPTION) {
					File file = fileChooser.getSelectedFile();
					try {
						System.out.println(file.getAbsolutePath());
					} catch (Exception exc) {
						exc.printStackTrace();
					}
				}
			}
		}

		@Override
		public void stateChanged(ChangeEvent event) {
			JSpinner spin = (JSpinner) event.getSource();
			simulationTimeDelay = (Integer) spin.getValue();
			System.out.println(simulationTimeDelay);
		}
	}

	/**
	 * Log method for test
	 * 
	 * @param message
	 *            the message to log
	 */

	private void log(String message) {
		Date d = new Date();
		System.out.println("[LOG : " + d.toString() + "] :: " + message);
	}
}